package com.imis.base.aspect;

import cn.hutool.core.util.NumberUtil;
import com.imis.base.annotation.VerificationCaptcha;
import com.imis.base.constant.CommonConstant;
import com.imis.base.constant.enums.SysTipEnum;
import com.imis.base.constant.enums.VerificationCodeTypeEnum;
import com.imis.base.globle.CustomizeException;
import com.imis.base.util.ConvertUtils;
import com.imis.base.util.RedisUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * <p>
 * VerificationCaptchaAspect<br>
 * 验证码验证 拦截器
 * </p>
 *
 * @author XinLau
 * @version 1.0
 * @since 2020年03月31日 15:57
 */
@Aspect
@Component
public class VerificationCaptchaAspect extends BaseAspect {

    /**
     * Redis 工具类
     */
    @Resource
    private RedisUtil redisUtil;

    /**
     * 最值误差值
     */
    private final static Double MAX_ERROR = 5.0D;
    private final static Double MIN_ERROR = -5.0D;

    /**
     * 自定义切点
     *
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/7/31 18:00
     */
    @Pointcut("@annotation(com.imis.base.annotation.VerificationCaptcha)")
    public void pointCut() {
    }

    /**
     * 验证码验证
     *
     * @param joinPoint - JoinPoint
     * @return Object - 返回值
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/25 09:35
     */
    @Before("pointCut()")
    public void verificationCaptcha(JoinPoint joinPoint) {
//        1.获取被拦截方法对象
        Method method = getMethod(joinPoint);
//        2.获取 Annotation
        VerificationCaptcha verificationCaptcha = method.getAnnotation(VerificationCaptcha.class);
//        3.是否开启验证码验证策略
        boolean verification = verificationCaptcha.verification();
        if (verification) {
//            4.验证码
            String captcha = generateStringBySpEL(verificationCaptcha.captcha(), joinPoint);
            VerificationCodeTypeEnum verificationCodeTypeEnum = verificationCaptcha.type();
//            验证码类型
            String type = verificationCodeTypeEnum.name();
//            验证码标识
            String identification = generateStringBySpEL(verificationCaptcha.verificationCodeIdentification(), joinPoint);
//            Redis的验证码缓存Key
            String key = CommonConstant.KAPTCHA_SESSION_KEY + CommonConstant.UNDERSCORE + type + CommonConstant.UNDERSCORE + identification;
//            6.读取缓存
            Object object = redisUtil.get(key);
            if (ConvertUtils.isEmpty(object)) {
                throw new CustomizeException(SysTipEnum.VERIFICATION_CODE_ERR_INVALID.toString());
            }
//            7.1.滑块验证码
            if (VerificationCodeTypeEnum.SLIDE.name().equals(type)) {
                if (!doErrorVerification(captcha, object)) {
//                    清除验证码缓存
                    redisUtil.del(key);
                    throw new CustomizeException(SysTipEnum.VERIFICATION_CODE_ERR_FAIL.toString());
                }
            }
//            7.2.字符、运算、短信、邮箱验证码
            if (!captcha.equals(object)) {
                if (!VerificationCodeTypeEnum.SMS.name().equals(type) || !VerificationCodeTypeEnum.EMAIL.name().equals(type)) {
//                    短信、邮箱验证码之外的清除验证码缓存
                    redisUtil.del(key);
                }
                throw new CustomizeException(SysTipEnum.VERIFICATION_CODE_ERR_FAIL.toString());
            }
//            8.清除验证通过的验证码缓存
            redisUtil.del(key);
        }
    }

    /**
     * 验证滑块验证码是否正确
     *
     * @param captcha - 前端输入的验证码
     * @param object  - 缓存中存放的验证码
     * @return Boolean
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/4/2 18:14
     */
    private Boolean doErrorVerification(String captcha, Object object) {
        String redisValue = String.valueOf(object);
//        缓存验证码坐标
        String[] redisCoordinate = redisValue.split(CommonConstant.COMMA);
//        输入的验证码坐标
        String[] captchaCoordinate = captcha.split(CommonConstant.COMMA);
        if (captchaCoordinate.length != redisCoordinate.length) {
            return Boolean.FALSE;
        }
//        X坐标
        Double xError = NumberUtil.sub(Double.valueOf(redisCoordinate[0]), Double.valueOf(captchaCoordinate[0]));
        if (MAX_ERROR < xError && xError > MIN_ERROR) {
            return Boolean.FALSE;
        }
//        Y坐标
        Double yError = NumberUtil.sub(Double.valueOf(redisCoordinate[1]), Double.valueOf(captchaCoordinate[1]));
        if (MAX_ERROR < yError && yError > MIN_ERROR) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

}
